﻿/*!
 * Copyright (c), 上海沃恩信息科技有限公司.
 * All rights reserved.
 * Licensed under BSD (https://www.pittypat.work/bsd.html).
 */

using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
using System.Data.SqlClient;

namespace Pittypat.Data
{
    /// <summary>
    /// 针对 SQL SERVER 数据库的数据服务。
    /// </summary>
    public sealed class SqlServerService : DataService
    {
        // 数据库连接字符串
        private string connectionString;

        /// <summary>
        /// 使用指定的连接字符串初始化 <see cref="SqlServerService"/> 类的新实例。
        /// </summary>
        /// <param name="connectionString">访问数据库的连接字符串。</param>
        /// <exception cref="ArgumentException">connectionString 为 null 或空字符串。</exception>
        internal SqlServerService(string connectionString)
        {
            if (string.IsNullOrEmpty(connectionString))
            {
                throw new ArgumentException("没有提供有效的连接字符串。", "connectionString");
            }

            this.connectionString = connectionString;
        }

        /// <summary>
        /// 根据参数属性的反射信息创建一个新的 SqlParameter 对象实例。
        /// </summary>
        /// <typeparam name="TArgs">功能实体类 FunctionArgs 的类型。</typeparam>
        /// <param name="meta">参数属性的反射信息。</param>
        /// <param name="model">获取参数值的 FunctionModel 对象实例。</param>
        /// <returns>新创建的 SqlParameter 对象实例。如果参数类型不是简单类型，则返回 null。</returns>
        static SqlParameter CreateParameter<TArgs>(ParameterMeta meta, FunctionModel<TArgs> model) where TArgs : FunctionArgs, new()
        {
            SqlParameter parameter = null;

            // 参数类型
            var dataType = meta.DataType;
            // 参数集合
            var args = model.Args;

            if (dataType == typeof(@string))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.VarChar);

                if (meta.Ascii)
                {
                    parameter.SqlDbType = meta.MaxLength > 0 && meta.MaxLength <= 8000 && meta.FixedLength ? SqlDbType.Char : SqlDbType.VarChar;
                }
                else
                {
                    parameter.SqlDbType = meta.MaxLength > 0 && meta.MaxLength <= 4000 && meta.FixedLength ? SqlDbType.NChar : SqlDbType.NVarChar;
                }

                parameter.Size = meta.MaxLength;
                parameter.IsNullable = false;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    parameter.Value = ((IPrimitive)args[meta.Identifier]).Value;
                }
            }
            else if (dataType == typeof(@string?))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.VarChar);

                if (meta.Ascii)
                {
                    parameter.SqlDbType = meta.MaxLength > 0 && meta.MaxLength <= 8000 && meta.FixedLength ? SqlDbType.Char : SqlDbType.VarChar;
                }
                else
                {
                    parameter.SqlDbType = meta.MaxLength > 0 && meta.MaxLength <= 4000 && meta.FixedLength ? SqlDbType.NChar : SqlDbType.NVarChar;
                }

                parameter.Size = meta.MaxLength;
                parameter.IsNullable = true;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    var v = ((@string?)args[meta.Identifier]);
                    parameter.Value = v.HasValue ? ((IPrimitive)v.Value).Value : DBNull.Value;
                }
            }
            else if (dataType == typeof(@binary))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.VarBinary);

                parameter.SqlDbType = meta.MaxLength > 0 && meta.MaxLength <= 8000 ? SqlDbType.Binary : SqlDbType.VarBinary;

                parameter.Size = meta.MaxLength;
                parameter.IsNullable = false;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    parameter.Value = ((IPrimitive)args[meta.Identifier]).Value;
                }
            }
            else if (dataType == typeof(@binary?))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.VarBinary);

                parameter.SqlDbType = meta.MaxLength > 0 && meta.MaxLength <= 8000 ? SqlDbType.Binary : SqlDbType.VarBinary;

                parameter.Size = meta.MaxLength;
                parameter.IsNullable = true;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    var v = ((@binary?)args[meta.Identifier]);
                    parameter.Value = v.HasValue ? ((IPrimitive)v.Value).Value : DBNull.Value;
                }
            }
            else if (dataType == typeof(@int32?))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.Int);

                parameter.IsNullable = true;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    var v = ((@int32?)args[meta.Identifier]);
                    parameter.Value = v.HasValue ? ((IPrimitive)v.Value).Value : DBNull.Value;
                }
            }
            else if (dataType == typeof(@uuid?))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.UniqueIdentifier);

                parameter.IsNullable = true;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    var v = ((@uuid?)args[meta.Identifier]);
                    parameter.Value = v.HasValue ? (Guid)v.Value : (object)DBNull.Value;
                }
            }
            else if (dataType == typeof(@bool?))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.Bit);

                parameter.IsNullable = true;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    var v = ((@bool?)args[meta.Identifier]);
                    parameter.Value = v.HasValue ? ((IPrimitive)v.Value).Value : DBNull.Value;
                }
            }
            else if (dataType == typeof(@datetime?))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.DateTime2);

                parameter.IsNullable = true;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    var v = ((@datetime?)args[meta.Identifier]);
                    parameter.Value = v.HasValue ? ((IPrimitive)v.Value).Value : DBNull.Value;
                }
            }
            else if (dataType == typeof(@date?))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.Date);

                parameter.IsNullable = true;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    var v = ((@date?)args[meta.Identifier]);
                    parameter.Value = v.HasValue ? ((IPrimitive)v.Value).Value : DBNull.Value;
                }
            }
            else if (dataType == typeof(@decimal?))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.Decimal);

                parameter.Precision = 28;
                parameter.Scale = (byte)(meta.MaxLength < 0 ? 0 : (meta.MaxLength > 27 ? 27 : meta.MaxLength));
                parameter.IsNullable = true;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    var v = ((@int32?)args[meta.Identifier]);
                    parameter.Value = v.HasValue ? ((IPrimitive)v.Value).Value : DBNull.Value;
                }
            }
            else if (dataType == typeof(@float32?))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.Real);

                parameter.IsNullable = true;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    var v = ((@float32?)args[meta.Identifier]);
                    parameter.Value = v.HasValue ? ((IPrimitive)v.Value).Value : DBNull.Value;
                }
            }
            else if (dataType == typeof(@float64?))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.Float);

                parameter.IsNullable = true;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    var v = ((@float64?)args[meta.Identifier]);
                    parameter.Value = v.HasValue ? ((IPrimitive)v.Value).Value : DBNull.Value;
                }
            }
            else if (dataType == typeof(@int8?))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.TinyInt);

                parameter.IsNullable = true;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    var v = ((@int8?)args[meta.Identifier]);
                    parameter.Value = v.HasValue ? ((IPrimitive)v.Value).Value : DBNull.Value;
                }
            }
            else if (dataType == typeof(@int16?))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.SmallInt);

                parameter.IsNullable = true;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    var v = ((@int16?)args[meta.Identifier]);
                    parameter.Value = v.HasValue ? ((IPrimitive)v.Value).Value : DBNull.Value;
                }
            }
            else if (dataType == typeof(@int64?))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.BigInt);

                parameter.IsNullable = true;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    var v = ((@int64?)args[meta.Identifier]);
                    parameter.Value = v.HasValue ? ((IPrimitive)v.Value).Value : DBNull.Value;
                }
            }
            else if (dataType == typeof(@interval?))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.BigInt);

                parameter.IsNullable = true;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    var v = ((@interval?)args[meta.Identifier]);
                    parameter.Value = v.HasValue ? ((IPrimitive)v.Value).Value : DBNull.Value;
                }
            }
            else if (dataType == typeof(@time?))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.Int);

                parameter.IsNullable = true;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    var v = ((@time?)args[meta.Identifier]);
                    parameter.Value = v.HasValue ? ((IPrimitive)v.Value).Value : DBNull.Value;
                }
            }
            else if (dataType == typeof(@int32))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.Int);

                parameter.IsNullable = false;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    parameter.Value = ((IPrimitive)args[meta.Identifier]).Value;
                }
            }
            else if (dataType == typeof(@bool))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.Bit);

                parameter.IsNullable = false;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    parameter.Value = ((IPrimitive)args[meta.Identifier]).Value;
                }
            }
            else if (dataType == typeof(@uuid))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.UniqueIdentifier);
                
                parameter.IsNullable = false;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    parameter.Value = (Guid)((@uuid)args[meta.Identifier]);
                }
            }
            else if (dataType == typeof(@date))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.Date);

                parameter.IsNullable = false;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    parameter.Value = ((IPrimitive)args[meta.Identifier]).Value;
                }
            }
            else if (dataType == typeof(@datetime))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.DateTime2);

                parameter.IsNullable = false;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    parameter.Value = ((IPrimitive)args[meta.Identifier]).Value;
                }
            }
            else if (dataType == typeof(@float32))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.Real);

                parameter.IsNullable = false;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    parameter.Value = ((IPrimitive)args[meta.Identifier]).Value;
                }
            }
            else if (dataType == typeof(@float64))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.Float);

                parameter.IsNullable = false;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    parameter.Value = ((IPrimitive)args[meta.Identifier]).Value;
                }
            }
            else if (dataType == typeof(@int8))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.TinyInt);

                parameter.IsNullable = false;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    parameter.Value = ((IPrimitive)args[meta.Identifier]).Value;
                }
            }
            else if (dataType == typeof(@int16))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.SmallInt);

                parameter.IsNullable = false;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    parameter.Value = ((IPrimitive)args[meta.Identifier]).Value;
                }
            }
            else if (dataType == typeof(@int64))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.BigInt);

                parameter.IsNullable = false;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    parameter.Value = ((IPrimitive)args[meta.Identifier]).Value;
                }
            }
            else if (dataType == typeof(@interval))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.BigInt);

                parameter.IsNullable = false;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    parameter.Value = ((IPrimitive)args[meta.Identifier]).Value;
                }
            }
            else if (dataType == typeof(@time))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.Int);

                parameter.IsNullable = false;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    parameter.Value = ((IPrimitive)args[meta.Identifier]).Value;
                }
            }
            else if (dataType == typeof(@decimal))
            {
                parameter = new SqlParameter("@" + meta.CamelIdentifier, SqlDbType.Decimal);

                parameter.Precision = 28;
                parameter.Scale = (byte)(meta.MaxLength < 0 ? 0 : (meta.MaxLength > 27 ? 27 : meta.MaxLength));
                parameter.IsNullable = false;

                if (meta.Direction == ParameterDirection.In || meta.Direction == ParameterDirection.InOut)
                {
                    parameter.Value = ((IPrimitive)args[meta.Identifier]).Value;
                }
            }

            if (parameter != null)
            {
                // 參數方向
                switch (meta.Direction)
                {
                    case ParameterDirection.Out:
                        parameter.Direction = System.Data.ParameterDirection.Output;
                        break;
                    case ParameterDirection.InOut:
                        parameter.Direction = System.Data.ParameterDirection.InputOutput;
                        break;
                    default:
                        parameter.Direction = System.Data.ParameterDirection.Input;
                        break;
                }
            }

            return parameter;
        }

        /// <summary>
        /// 使用目标数据库连接以及功能信息创建一个新的命令对象。
        /// </summary>
        /// <typeparam name="TArgs">功能实体类 FunctionArgs 的类型。</typeparam>
        /// <param name="connection">数据库连接对象。</param>
        /// <param name="transaction">当前事务。</param>
        /// <param name="meta">功能的配置信息和参数信息。</param>
        /// <param name="model">用于获取参数值的 FunctionModel 对象。</param>
        /// <param name="needReader">返回一个值，该值指示在命令执行完成后是否需要通过 DataReader 读取结果集合。如果参数列表中包含了非简单类型，则返回 true，否则返回 false。</param>
        /// <returns>数据库命令。</returns>
        static SqlCommand CreateCommand<TArgs>(SqlConnection connection, SqlTransaction transaction, FunctionMeta meta, FunctionModel<TArgs> model, out bool needReader) where TArgs : FunctionArgs, new()
        {
            needReader = false;
            var command = connection.CreateCommand();
            command.Connection = connection;
            command.CommandType = CommandType.StoredProcedure;
            command.CommandText = meta.SpQualifiedName;

            // 如果启用了事务，为命令设置事务
            if (transaction != null)
            {
                command.Transaction = transaction;
            }

            // 为命令添加输入输出的简单类型的参数
            if (meta.Parameters.Length > 0)
            {
                SqlParameter param = null;
                for (int i = 0; i < meta.Parameters.Length; ++i)
                {
                    param = CreateParameter(meta.Parameters[i], model);
                    if (param != null)
                    {
                        command.Parameters.Add(param);
                    }
                    else
                    {
                        // 对于 DataModel 或 DataList 的复杂类型参数，需要在命令完成后使用 DataReader 读取结果集合
                        needReader = true;
                    }
                }
            }

            // _context，以 JSON 格式向数据库传递当前上下文信息，比如 IP 地址等，SQL 编码人员可以借助数据库提供的 JSON 解析函数解析这些信息
            command.Parameters.Add(new SqlParameter("@_context", SqlDbType.NVarChar)
            {
                Direction = System.Data.ParameterDirection.Input,
                IsNullable = false,
                Size = 4000,
                Value = model.Context == null ? "" : model.Context.ToString()
            });

            // _success，返回操作是否成功的指示
            command.Parameters.Add(new SqlParameter("@_success", SqlDbType.Bit)
            {
                Direction = System.Data.ParameterDirection.Output,
                IsNullable = false
            });

            // _message，如果失败，返回错误消息
            command.Parameters.Add(new SqlParameter("@_message", SqlDbType.NVarChar)
            {
                Direction = System.Data.ParameterDirection.Output,
                IsNullable = false,
                Size = 4000
            });

            return command;
        }

        /// <summary>
        /// 在命令执行完成后，设置从数据库命令返回的输出参数，不包括 DataModel 和 DataList 参数。
        /// </summary>
        /// <typeparam name="TArgs">FunctionArgs 类型。</typeparam>
        /// <param name="parameters">参数列表。</param>
        /// <param name="model">设置参数值的 FunctionModel 对象实例。</param>
        /// <param name="command">目标数据库命令。</param>
        static void SetOut<TArgs>(IList<ParameterMeta> parameters, FunctionModel<TArgs> model, SqlCommand command) where TArgs : FunctionArgs, new()
        {
            if (parameters != null && parameters.Count > 0)
            {
                var args = model.Args;
                Type dataType = null;
                foreach (var mp in parameters)
                {
                    if (mp.Direction == ParameterDirection.InOut || mp.Direction == ParameterDirection.Out)
                    {
                        dataType = mp.DataType;
                        if (dataType == typeof(@string))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@string>() ?? args[mp.Identifier];
                        }
                        else if (dataType == typeof(@string?))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@string>();
                        }
                        else if (dataType == typeof(@int32))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@int32>() ?? args[mp.Identifier];
                        }
                        else if (dataType == typeof(@int32?))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@int32>();
                        }
                        else if (dataType == typeof(@uuid))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@uuid>() ?? args[mp.Identifier];
                        }
                        else if (dataType == typeof(@uuid?))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@uuid>();
                        }
                        else if (dataType == typeof(@bool))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@bool>() ?? args[mp.Identifier];
                        }
                        else if (dataType == typeof(@bool?))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@bool>();
                        }
                        else if (dataType == typeof(@binary))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@binary>() ?? args[mp.Identifier];
                        }
                        else if (dataType == typeof(@binary?))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@binary>();
                        }
                        else if (dataType == typeof(@int8))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@int8>() ?? args[mp.Identifier];
                        }
                        else if (dataType == typeof(@int8?))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@int8>();
                        }
                        else if (dataType == typeof(@int16))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@int16>() ?? args[mp.Identifier];
                        }
                        else if (dataType == typeof(@int16?))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@int16>();
                        }
                        else if (dataType == typeof(@int64))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@int64>() ?? args[mp.Identifier];
                        }
                        else if (dataType == typeof(@int64?))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@int64>();
                        }
                        else if (dataType == typeof(@date))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@date>() ?? args[mp.Identifier];
                        }
                        else if (dataType == typeof(@date?))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@date>();
                        }
                        else if (dataType == typeof(@datetime))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@datetime>() ?? args[mp.Identifier];
                        }
                        else if (dataType == typeof(@datetime?))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@datetime>();
                        }
                        else if (dataType == typeof(@decimal))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@decimal>() ?? args[mp.Identifier];
                        }
                        else if (dataType == typeof(@decimal?))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@decimal>();
                        }
                        else if (dataType == typeof(@float32))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@float32>() ?? args[mp.Identifier];
                        }
                        else if (dataType == typeof(@float32?))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@float32>();
                        }
                        else if (dataType == typeof(@float64))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@float64>() ?? args[mp.Identifier];
                        }
                        else if (dataType == typeof(@float64?))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@float64>();
                        }
                        else if (dataType == typeof(@time))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@time>() ?? args[mp.Identifier];
                        }
                        else if (dataType == typeof(@time?))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@time>();
                        }
                        else if (dataType == typeof(@interval))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@interval>() ?? args[mp.Identifier];
                        }
                        else if (dataType == typeof(@interval?))
                        {
                            args[mp.Identifier] = command.Parameters["@" + mp.CamelIdentifier].Value.To<@interval>();
                        }
                    }
                }
            }

            // _success 参数
            model.Success = command.Parameters["@_success"].Value.To<@bool>() ?? false;
            // _message 参数
            model.Message = command.Parameters["@_message"].Value.To<@string>() ?? "";
        }

        /// <summary>
        /// 在命令执行完成后，设置从数据库命令返回的输出参数，只读取 DataModel 和 DataList 参数。
        /// </summary>
        /// <param name="meta">功能的配置信息和参数信息。</param>
        /// <param name="args">要设置参数值的 FunctionArgs 对象。</param>
        /// <param name="reader">命令返回的 DataReader。</param>
        /// <param name="progress">跟踪和汇报异步操作的进度。</param>
        /// <returns>需要进一步读取参数值的基础类型参数列表。</returns>
        static async Task<List<ParameterMeta>> ReadOutAsync(FunctionMeta meta, FunctionArgs args, SqlDataReader reader, IAsyncProgress progress)
        {
            if (meta.Parameters.Length > 0)
            {
                var primList = new List<ParameterMeta>(meta.Parameters.Length);

                // 指示是否正在读取 DataReader 中的第一个结果集合
                bool firstResult = true;
                foreach (var mp in meta.Parameters)
                {
                    if (mp.Direction == ParameterDirection.InOut || mp.Direction == ParameterDirection.Out)
                    {
                        if (mp.DataType.IsSubclassOf(typeof(DataModel)))
                        {
                            // 单个 DataModel 对象
                            if (firstResult)
                            {
                                // 读取第一个结果集合的第一行数据
                                if (await reader.ReadAsync(progress.Token) && reader.HasRows)
                                {
                                    // 功能实体对象的参数属性中包含的原始 DataModel 对象
                                    var dm = (DataModel)args[mp.Identifier];
                                    if (dm == null)
                                    {
                                        // 如果原 DataModel 对象是 null，通过反射创建一个新的对象
                                        dm = (DataModel)Activator.CreateInstance(mp.DataType);
                                        args[mp.Identifier] = dm;
                                    }

                                    // 从 reader 中读取 DataModel
                                    dm.Read(reader);
                                }

                                firstResult = false;
                            }
                            else
                            {
                                // 不是第一个结果集合
                                if (await reader.NextResultAsync(progress.Token) && await reader.ReadAsync(progress.Token) && reader.HasRows)
                                {
                                    var dm = (DataModel)args[mp.Identifier];
                                    if (dm == null)
                                    {
                                        dm = (DataModel)Activator.CreateInstance(mp.DataType);
                                        args[mp.Identifier] = dm;
                                    }

                                    dm.Read(reader);
                                }
                            }
                        }
                        else if (mp.DataType.IsGenericType && mp.DataType.GetGenericTypeDefinition() == typeof(DataList<>))
                        {
                            // 从 DataReader 当前结果集合中读取一个 DataList
                            if (firstResult)
                            {
                                // 第一个结果集合
                                var list = args[mp.Identifier];
                                if (list == null)
                                {
                                    // 如果原 DataList 为 null，则创建一个新的 DataList 对象
                                    list = Activator.CreateInstance(mp.DataType, 20 /* capacity */);
                                    args[mp.Identifier] = list;
                                }

                                // 通过反射执行 DataList 上的 ReadAsync 方法
                                await (Task)mp.DataType.GetMethod("ReadAsync").Invoke(list, new object[] { reader, progress.Token });

                                firstResult = false;
                            }
                            else
                            {
                                // 不是第一个结果集合
                                var list = args[mp.Identifier];
                                if (list == null)
                                {
                                    list = Activator.CreateInstance(mp.DataType, 20 /* capacity */);
                                    args[mp.Identifier] = list;
                                }

                                if (await reader.NextResultAsync(progress.Token))
                                {
                                    await (Task)mp.DataType.GetMethod("ReadAsync").Invoke(list, new object[] { reader, progress.Token });
                                }
                            }
                        }
                        else
                        {
                            // 基础类型
                            primList.Add(mp);
                        }
                    }
                }

                return primList;
            }

            return null;
        }

        /// <summary>
        /// 异步执行指定功能对应的数据操作命令。
        /// </summary>
        /// <typeparam name="TArgs">功能实体类 FunctionArgs 的类型。</typeparam>
        /// <param name="model">功能模型对象，其中包含了功能的参数列表、执行状态以及相关消息。</param>
        /// <param name="progress">跟踪和汇报异步操作的进度。</param>
        /// <returns>异步操作任务。</returns>
        /// <exception cref="ArgumentNullException">model 或 progress 为 null。</exception>
        public async override Task Exec<TArgs>(FunctionModel<TArgs> model, IAsyncProgress progress)
        {
            if (model == null)
            {
                throw new ArgumentNullException("model");
            }

            if (progress == null)
            {
                throw new ArgumentNullException("progress");
            }

            // 参数列表和配置信息
            var meta = FunctionMeta.Find(typeof(TArgs));
            if (meta == null)
            {
                throw new InvalidOperationException("没有找到 " + typeof(TArgs).FullName + " 对应的功能配置信息。");
            }

            // 指示命令是否返回 DataReader
            var needReader = false;

            // 创建数据库连接
            using (var connection = new SqlConnection(this.connectionString))
            {
                // 异步打开数据库连接
                await connection.OpenAsync(progress.Token);

                if (meta.Attribute.Transaction)
                {
                    // 启动事务
                    var transaction = connection.BeginTransaction();

                    try
                    {
                        // 在事务中执行命令
                        using (var command = CreateCommand(connection, transaction, meta, model, out needReader))
                        {
                            IList<ParameterMeta> primList = null;
                            if (needReader)
                            {
                                // 执行命令，返回一个 DataReader
                                using (var reader = await command.ExecuteReaderAsync(progress.Token))
                                {
                                    // 从 DataReader 中读取 DataModel 和 DataList
                                    primList = await ReadOutAsync(meta, model.Args, reader, progress);
                                }
                            }
                            else
                            {
                                // 执行命令，不需要返回 DataReader
                                await command.ExecuteNonQueryAsync(progress.Token);
                                primList = meta.Parameters;
                            }

                            // 设置基础类型的输出参数
                            SetOut(primList, model, command);
                        }

                        // 提交事务
                        transaction.Commit();
                    }
                    catch (Exception exc)
                    {
                        // 回滚事务
                        transaction.Rollback();
                        throw new TransactionRollbackException(exc);
                    }
                }
                else
                {
                    // 不在事务中执行命令
                    using (var command = CreateCommand(connection, null, meta, model, out needReader))
                    {
                        IList<ParameterMeta> primList = null;
                        if (needReader)
                        {
                            // 执行命令，返回一个 DataReader
                            using (var reader = await command.ExecuteReaderAsync(progress.Token))
                            {
                                // 从 DataReader 中读取 DataModel 和 DataList
                                primList = await ReadOutAsync(meta, model.Args, reader, progress);
                            }
                        }
                        else
                        {
                            // 执行命令，不需要返回 DataReader
                            await command.ExecuteNonQueryAsync(progress.Token);
                            primList = meta.Parameters;
                        }

                        // 设置基础类型的输出参数
                        SetOut(primList, model, command);
                    }
                }
            }
        }
    }
}
